﻿// Copyright (c)  YISH. All rights reserved.
// Licensed under the MIT License, See License.txt in the project root for license information.

using System;
using System.Data;
using System.Diagnostics;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using NSQL.Expressions;
using NSQL.Expressions.Functions;
using LinqConditionalExpression = System.Linq.Expressions.ConstantExpression;

namespace NSQL.Query.Compiler
{
    public class ExpressionCompiler : IExpressionVisitor<Expression>
    {
        private static readonly ParameterExpression DataRow;
        private static readonly LinqConditionalExpression Null;
        private static readonly MethodInfo IsNull;

        static ExpressionCompiler()
        {
            Null = Expression.Constant(DBNull.Value);
            var dataRowType = typeof(DataRow);
            DataRow = Expression.Variable(dataRowType, "dataRow");
            IsNull = dataRowType.GetMethods(BindingFlags.Instance | BindingFlags.Public)
                .First(o => o.Name == nameof(IsNull) && o.ToString() == "Boolean IsNull(System.String)");
        }

        private readonly DataTable _dataTable;

        public ExpressionCompiler(DataTable dataTable)
        {
            _dataTable = dataTable ?? throw new ArgumentNullException(nameof(dataTable));
        }
        public Expression<Func<DataRow, bool>> Complile(IPredicateExpression predicateExpression)
        {
            return Expression.Lambda<Func<DataRow, bool>>(predicateExpression.Accept(this), DataRow);
        }


        Expression IFunctionVisitor<Expression>.Visit(ICountFunction countFunction)
        {
            throw new System.NotImplementedException();
        }

        Expression IFunctionVisitor<Expression>.Visit(IRoundFunction roundFunction)
        {
            throw new System.NotImplementedException();
        }

        Expression IFunctionVisitor<Expression>.Visit(ISumFunction sumFunction)
        {
            throw new System.NotImplementedException();
        }

        Expression IFunctionVisitor<Expression>.Visit(IMaxFunction maxFunction)
        {
            throw new System.NotImplementedException();
        }

        Expression IFunctionVisitor<Expression>.Visit(IAvgFunction avgFunction)
        {
            throw new System.NotImplementedException();
        }

        Expression IFunctionVisitor<Expression>.Visit(IMinFunction minFunction)
        {
            throw new System.NotImplementedException();
        }

        public Expression Visit(IMedianFunction expression)
        {
            throw new NotImplementedException();
        }

        Expression IFunctionVisitor<Expression>.Visit(IStdDevFunction stdDevFunction)
        {
            throw new NotImplementedException();
        }

        Expression IFunctionVisitor<Expression>.Visit(IVarianceFunction varianceFunction)
        {
            throw new NotImplementedException();
        }

        Expression IFunctionVisitor<Expression>.Visit(IToCharFunction toCharFunction)
        {
            throw new System.NotImplementedException();
        }

        Expression IFunctionVisitor<Expression>.Visit(ITrimFunction trimFunction)
        {
            throw new System.NotImplementedException();
        }

        Expression IFunctionVisitor<Expression>.Visit(ILengthFunction lengthFunction)
        {
            throw new System.NotImplementedException();
        }

        Expression IFunctionVisitor<Expression>.Visit(ICastFunction castFunction)
        {
            throw new System.NotImplementedException();
        }

        Expression IFunctionVisitor<Expression>.Visit(IToNumberFunction toNumberFunction)
        {
            throw new System.NotImplementedException();
        }

        Expression IFunctionVisitor<Expression>.Visit(ISubStringFunction stringFunction)
        {
            throw new System.NotImplementedException();
        }

        Expression IFunctionVisitor<Expression>.Visit(INvlFunction nvlFunction)
        {
            throw new System.NotImplementedException();
        }

        Expression IFunctionVisitor<Expression>.Visit(IPadFunction padFunction)
        {
            throw new System.NotImplementedException();
        }

        public Expression Visit(ILeftFunction leftFunction)
        {
            throw new NotImplementedException();
        }

        public Expression Visit(IRightFunction rightFunction)
        {
            throw new NotImplementedException();
        }

        Expression IFunctionVisitor<Expression>.Visit(IUpperFunction upperFunction)
        {
            throw new System.NotImplementedException();
        }

        Expression IFunctionVisitor<Expression>.Visit(ILowerFunction lowerFunction)
        {
            throw new System.NotImplementedException();
        }

        Expression IFunctionVisitor<Expression>.Visit(IReplaceFunction replaceFunction)
        {
            throw new System.NotImplementedException();
        }

        Expression IFunctionVisitor<Expression>.Visit(IExtractFunction extractFunction)
        {
            throw new System.NotImplementedException();
        }

        Expression IFunctionVisitor<Expression>.Visit(IConcatFunction concatFunction)
        {
            throw new NotImplementedException();
        }

        Expression IFunctionVisitor<Expression>.Visit(IReverseFunction reverseFunction)
        {
            throw new NotImplementedException();
        }

        public Expression Visit(IToDatetimeFunction toDatetimeFunction)
        {
            throw new NotImplementedException();
        }

        public Expression Visit(IAddIntervalFunction addIntervalFunction)
        {
            throw new NotImplementedException();
        }

        public Expression Visit(IStartWithFunction startWithFunction)
        {
            throw new NotImplementedException();
        }

        public Expression Visit(IEndWithFunction endWithFunction)
        {
            throw new NotImplementedException();
        }

        public Expression Visit(IContainsFunction containsFunction)
        {
            throw new NotImplementedException();
        }

        public Expression Visit(ISinFunction sinFunction)
        {
            throw new NotImplementedException();
        }

        Expression IFunctionVisitor<Expression>.Visit(ICosFunction cosFunction)
        {
            throw new NotImplementedException();
        }

        Expression IFunctionVisitor<Expression>.Visit(ITanFunction tanFunction)
        {
            throw new NotImplementedException();
        }

        Expression IFunctionVisitor<Expression>.Visit(IAbsFunction absFunction)
        {
            throw new NotImplementedException();
        }

        Expression IFunctionVisitor<Expression>.Visit(ISignFunction signFunction)
        {
            throw new NotImplementedException();
        }

        public Expression Visit(ISqrtFunction expression)
        {
            throw new NotImplementedException();
        }

        public Expression Visit(ILogFunction expression)
        {
            throw new NotImplementedException();
        }

        public Expression Visit(IPowFunction expression)
        {
            throw new NotImplementedException();
        }

        public Expression Visit(IRankFunction expression)
        {
            throw new NotImplementedException();
        }

        public Expression Visit(ICoalesceFunction expression)
        {
            throw new NotImplementedException();
        }

        public Expression Visit(IDecodeFunction expression)
        {
            throw new NotImplementedException();
        }

        public Expression Visit(ICeilFunction expression)
        {
            throw new NotImplementedException();
        }

        public Expression Visit(IFloorFunction expression)
        {
            throw new NotImplementedException();
        }

        public Expression Visit(IPercentileFunction expression)
        {
            throw new NotImplementedException();
        }

        public Expression Visit(IFormatFunction expression)
        {
            throw new NotImplementedException();
        }

        public Expression Visit(IDiffFunction expression)
        {
            throw new NotImplementedException();
        }

        Expression IExpressionVisitor<Expression>.Visit(IComparisonExpression comparisonExpression)
        {
            var left = comparisonExpression.Left.Accept(this);
            var right = comparisonExpression.Right.Accept(this);
            switch (comparisonExpression.Operator)
            {
                case ComparisonOperator.Equal:
                    return Expression.Equal(left, right);
                case ComparisonOperator.NotEqual:
                    return Expression.NotEqual(left, right);
                case ComparisonOperator.Less:
                    return Expression.LessThan(left, right);
                case ComparisonOperator.Greater:
                    return Expression.GreaterThan(left, right);
                case ComparisonOperator.LessOrEqual:
                    return Expression.LessThanOrEqual(left, right);
                case ComparisonOperator.GreaterOrEqual:
                    return Expression.GreaterThanOrEqual(left, right);
                default:
                    throw new ArgumentOutOfRangeException();
            }
        }

        Expression IExpressionVisitor<Expression>.Visit(IColumnExpression columnExpression)
        {
            var columnName = Expression.Constant(columnExpression.Name.ToString());
            var isNull = Expression.Call(DataRow, IsNull, columnName);
            var column = Expression.Property(DataRow, "Item", columnName);
            var dataType = _dataTable.Columns[columnExpression.Name].DataType;
            return Expression.Convert(column, dataType);
        }

        Expression IExpressionVisitor<Expression>.Visit(IConstantExpression constantExpression)
        {
            return Expression.Constant(constantExpression.ValueOf());
        }

        Expression IExpressionVisitor<Expression>.Visit(IInExpression inExpression)
        {
            var value = inExpression.Left.Accept(this);
            var elements =  inExpression.Elements.Accept(this);
            var item = Expression.Parameter(typeof(string), "item");
            var predicate = Expression.Lambda(Expression.Equal(item, value), item);
            
            return ExpressionBuilder.Any(elements, predicate);
        }

        Expression IExpressionVisitor<Expression>.Visit(INotInExpression notInExpression)
        {
            var value = notInExpression.Left.Accept(this);
            var elements =  notInExpression.Elements.Accept(this);
            var item = Expression.Parameter(typeof(string), "item");
            var predicate = Expression.Lambda(Expression.Equal(item, value), item);
            return Expression.Not(ExpressionBuilder.Any(elements, predicate));
        }

        Expression IExpressionVisitor<Expression>.Visit(ILogicalExpression groupExpression)
        {
            var left = groupExpression.Left.Accept(this);
            var right = groupExpression.Right.Accept(this);
            switch (groupExpression.Operator)
            {
                case LogicalOperator.And:
                    return Expression.AndAlso(left, right);
                case LogicalOperator.Or:
                    return Expression.OrElse(left, right);
                default:
                    throw new ArgumentOutOfRangeException();
            }
        }

        Expression IExpressionVisitor<Expression>.Visit(IBetweenExpression betweenExpression)
        {
            var value = betweenExpression.Left.Accept(this);
            var begin = betweenExpression.Begin.Accept(this);
            var end = betweenExpression.End.Accept(this);
            return Expression.And(Expression.LessThanOrEqual(begin, value), Expression.LessThanOrEqual(value, end));
        }

        Expression IExpressionVisitor<Expression>.Visit(INotExpression notExpression)
        {
            return Expression.Not(notExpression.Expr.Accept(this));
        }

        Expression IExpressionVisitor<Expression>.Visit(INullExpression nullExpression)
        {
            if (nullExpression.Expr is IColumnExpression columnExression)
            {
                var columnName = Expression.Constant(columnExression.Name);
                var isNull = Expression.Call(DataRow, IsNull, columnName);
                return isNull;
            }
            
            var value = nullExpression.Expr.Accept(this);
            return Expression.Equal(value, Null);
        }

        Expression IExpressionVisitor<Expression>.Visit(INotNullExpression notNullExpression)
        {
            if (notNullExpression.Expr is IColumnExpression columnExression)
            {
                var columnName = Expression.Constant(columnExression.Name.ToString());
                var isNull = Expression.Call(DataRow, IsNull, columnName);
                return Expression.Not(isNull);
            }
            
            var value = notNullExpression.Expr.Accept(this);
            return Expression.Not(Expression.Equal(value, Null));
        }

        Expression IExpressionVisitor<Expression>.Visit(IMatchExpression matchExpression)
        {
            throw new System.NotImplementedException();
        }

        Expression IExpressionVisitor<Expression>.Visit(ISimpleMatchExpression simpleMatchExpression)
        {
            throw new System.NotImplementedException();
        }

        Expression IExpressionVisitor<Expression>.Visit(IArithmeticExpression arithmeticExpression)
        {
            var left = arithmeticExpression.Left.Accept(this);
            var right = arithmeticExpression.Right.Accept(this);
            switch (arithmeticExpression.Operator)
            {
                case ArithmeticOperator.Plus:
                    return Expression.Add(left, right);
                case ArithmeticOperator.Minus:
                    return Expression.Subtract(left, right);
                case ArithmeticOperator.Multiply:
                    return Expression.Multiply(left, right);
                case ArithmeticOperator.Divide:
                    return Expression.Divide(left, right);
                default:
                    throw new ArgumentOutOfRangeException();
            }
        }

        Expression IExpressionVisitor<Expression>.Visit(IUnaryExpression unaryExpression)
        {
            throw new System.NotImplementedException();
        }

        Expression IExpressionVisitor<Expression>.Visit(ISystemVariable systemVariable)
        {
            throw new System.NotImplementedException();
        }

        Expression IExpressionVisitor<Expression>.Visit(ILikeExpression likeExpression)
        {
            throw new System.NotImplementedException();
        }

        Expression IExpressionVisitor<Expression>.Visit(IExpressions inElements)
        {
            Debug.Assert(inElements.Count > 0);
            var elments = inElements.Select(o => o.Accept(this)).ToList();
            return Expression.NewArrayInit(elments[0].Type, elments);
        }

        public Expression Visit(IExistsExpression existsExpression)
        {
            throw new NotImplementedException();
        }

        IFunctionVisitor<Expression> IExpressionVisitor<Expression>.FunctionVisitor => this;
    }

}